home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockFacebookAPI.js < prev    next >
Text File  |  2007-10-18  |  44KB  |  1,406 lines

  1. // BEGIN FLOCK GPL
  2. // 
  3. // Copyright Flock Inc. 2005-2007
  4. // http://flock.com
  5. // 
  6. // This file may be used under the terms of of the
  7. // GNU General Public License Version 2 or later (the "GPL"),
  8. // http://www.gnu.org/licenses/gpl.html
  9. // 
  10. // Software distributed under the License is distributed on an "AS IS" basis,
  11. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. // for the specific language governing rights and limitations under the
  13. // License.
  14. // 
  15. // END FLOCK GPL
  16.  
  17. // Constants
  18. const CC = Components.classes;
  19. const CI = Components.interfaces;
  20. const CR = Components.results;
  21. const CU = Components.utils;
  22.  
  23. CU.import("resource:///modules/FlockCryptoHash.jsm");
  24.  
  25. const FACEBOOK_API_CID = Components.ID("{53b077f0-6155-11db-b0de-0800200c9a66}");
  26. const FACEBOOK_API_CONTRACTID = "@flock.com/api/facebook;1";
  27.  
  28. // From developer.facebook.com...
  29. const FACEBOOK_API_KEY = "7fa235c847b11f157e12586c86660f47";
  30. const FACEBOOK_API_SECRET = "645e5d3f9fde64b65a1a12744486b093";
  31. const FACEBOOK_API_VERSION = "1.0";
  32. const FACEBOOK_API_HOSTNAME = "http://api.facebook.com/";
  33. const FACEBOOK_API_ENDPOINT_URL = FACEBOOK_API_HOSTNAME + "restserver.php";
  34. const FACEBOOK_API_TOS_URL = "http://www.facebook.com/tos.php?api_key="
  35.                            + FACEBOOK_API_KEY
  36.                            + "&v=1.0";
  37. const FACEBOOK_API_SETSTATUS_URL = "http://www.facebook.com/updatestatus.php";
  38.  
  39. // From nsIXMLHttpRequest.idl
  40. // 0: UNINITIALIZED open() has not been called yet.
  41. // 1: LOADING       send() has not been called yet.
  42. // 2: LOADED        send() has been called, headers and status are available.
  43. // 3: INTERACTIVE   Downloading, responseText holds the partial data.
  44. // 4: COMPLETED     Finished with all operations.
  45. const XMLHTTPREQUEST_READYSTATE_UNINITIALIZED = 0;
  46. const XMLHTTPREQUEST_READYSTATE_LOADING = 1;
  47. const XMLHTTPREQUEST_READYSTATE_LOADED = 2;
  48. const XMLHTTPREQUEST_READYSTATE_INTERACTIVE = 3;
  49. const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;
  50.  
  51. const HTTP_CODE_OK = 200;
  52. const HTTP_CODE_FOUND = 302;
  53.  
  54. const XMLHTTPREQUEST_CONTRACTID = "@mozilla.org/xmlextras/xmlhttprequest;1";
  55. const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
  56.  
  57. const FLOCK_PHOTO_ALBUM_CONTRACTID  = "@flock.com/photo-album;1";
  58.  
  59. const FLOCK_SNOWMAN_URL = "chrome://browser/skin/flock/services/common/no_avatar.png";
  60.  
  61. function _getLoader() {
  62.   return CC["@mozilla.org/moz/jssubscript-loader;1"]
  63.          .getService(CI.mozIJSSubScriptLoader);
  64. }
  65.  
  66. function loadLibraryFromSpec(aSpec) {
  67.   _getLoader().loadSubScript(aSpec);
  68. }
  69.  
  70. loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");
  71.  
  72. function createEnum(array) {
  73.   return {
  74.     QueryInterface: function (iid) {
  75.       if (iid.equals(CI.nsISimpleEnumerator)) {
  76.         return this;
  77.       }
  78.       throw CR.NS_ERROR_NO_INTERFACE;
  79.     },
  80.     hasMoreElements: function() {
  81.       return (array.length>0);
  82.     },
  83.     getNext: function() {
  84.       return array.shift();
  85.     }
  86.   }
  87. }
  88.  
  89.  
  90. function MyBag() {
  91. }
  92.  
  93. MyBag.prototype.setProperty =
  94. function mybag_setProperty(aKey, aValue) {
  95.   this[aKey] = aValue;
  96. };
  97.  
  98. MyBag.prototype.setPropertyAsInt32 =
  99. function mybag_setPropertyAsInt32(aKey, aValue) {
  100.   this.setProperty(aKey, aValue);
  101. };
  102.  
  103. MyBag.prototype.setPropertyAsAString =
  104. function mybag_setPropertyAsString(aKey, aValue) {
  105.   this.setProperty(aKey, aValue);
  106. };
  107.  
  108. MyBag.prototype.getProperty =
  109. function mybag_getProperty(aKey) {
  110.   return this[aKey];
  111. };
  112.  
  113. MyBag.prototype.getPropertyAsInt32 =
  114. function mybag_getPropertyAsString(aKey) {
  115.   return this.getProperty(aKey);
  116. };
  117.  
  118. MyBag.prototype.getPropertyAsAString =
  119. function mybag_getPropertyAsString(aKey) {
  120.   return this.getProperty(aKey);
  121. };
  122.  
  123. MyBag.prototype.QueryInterface =
  124. function mybag_QueryInterface(aIid) {
  125.   return this;
  126. };
  127.  
  128. function _validateUid(aUid) {
  129.   if (aUid.match(/[^0-9]/)) {
  130.     throw "Invalid UID '" + aUid + "': the UID must be a numeric value";
  131.   }
  132. }
  133.  
  134. // ====================================================
  135. // ========== BEGIN flockIPhoto object       ==========
  136. // ====================================================
  137.  
  138. function facebookPhoto() {
  139. }
  140.  
  141. facebookPhoto.prototype = {
  142.   id: "",
  143.   thumbnail: "",
  144.   webPageUrl: "",
  145.   midSizePhoto: "",
  146.   largeSizePhoto: "",
  147.   title: "",
  148.   username: "",
  149.   userid: "",
  150.   is_public: true,
  151.   is_video: false,
  152.   svcShortName: "facebook",
  153.   buildTooltip: function facebookPhoto_buildTooltip() {
  154.     default xml namespace =
  155.         "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  156.     var tip =
  157.       <vbox>
  158.         <hbox>
  159.           <image src={this.midSizePhoto} style="margin-bottom: 2px;" />
  160.           <spacer flex="1" />
  161.         </hbox>
  162.         <hbox>
  163.           <image src={this.icon} />
  164.           <vbox style="max-width: 250px;">
  165.             <label crop="end">{this.title}</label>
  166.             <label class="user">{this.username}</label>
  167.           </vbox>
  168.         </hbox>
  169.       </vbox>;
  170.  
  171.     var parser = CC["@mozilla.org/xmlextras/domparser;1"]
  172.                  .createInstance(CI.nsIDOMParser);
  173.     var doc = parser.parseFromString(tip, "text/xml");
  174.     return doc.documentElement;
  175.   },
  176.  
  177.   buildHTML: function facebookPhoto_buildHTML() {
  178.     var xml =
  179.       <a title={this.title} href={this.webPageUrl}>
  180.         <img src={this.midSizePhoto} border="0" />
  181.       </a>
  182.     return xml;
  183.   },
  184.  
  185.   buildLargeHTML: function facebookPhoto_buildLargeHTML() {
  186.     var xml =
  187.       <a title={this.title} href={this.webPageUrl}>
  188.         <img src={this.largeSizePhoto} border="0" />
  189.       </a>
  190.     return xml;
  191.   },
  192.  
  193.   buildBBCode: function facebookPhoto_buildBBCode() {
  194.     return '[url=' + this.webPageUrl + '][img]'+ this.largeSizePhoto +'[/img][/url]';
  195.   },
  196.  
  197.   QueryInterface: function facebookPhoto_QueryInterface(aIid) {
  198.     if (!aIid.equals(CI.nsISupports) &&
  199.         !aIid.equals(CI.flockIPhoto)) {
  200.       throw CR.NS_ERROR_NO_INTERFACE;
  201.     }
  202.     return this;
  203.   }
  204. };
  205.  
  206. /**
  207.  * Represents the Facebook API
  208.  */
  209. function facebookAPI() {
  210.   this.api_key = FACEBOOK_API_KEY;
  211.   this.api_secret = FACEBOOK_API_SECRET;
  212.   this.endpoint = FACEBOOK_API_ENDPOINT_URL;
  213.   this.prettyName = "Facebook";
  214.   this.uid = null;
  215.   this._friendsCache = {
  216.     value: null,
  217.     lastUpdate: new Date(0)
  218.   };
  219.  
  220.   this.acUtils = CC["@flock.com/account-utils;1"]
  221.                  .getService(CI.flockIAccountUtils);
  222.   var obService = CC["@mozilla.org/observer-service;1"]
  223.                   .getService(CI.nsIObserverService);
  224.   var inst = this;
  225.   this.webDetective = CC["@flock.com/web-detective;1"]
  226.                       .getService(CI.flockIWebDetective);
  227.  
  228.   this._logger = CC["@flock.com/logger;1"].createInstance(CI.flockILogger);
  229.   this._logger.init("facebookAPI");
  230.   this.wrappedJSObject = this;
  231. }
  232.  
  233. //****************************************
  234. //* Authentication & Sesssion Calls
  235. //****************************************/
  236.  
  237. facebookAPI.prototype.sessionPing =
  238. function facebookAPI_sessionPing(aListener) {
  239.   var pingListener = {
  240.     onSuccess: function onSuccess(aResult) {
  241.       aListener.onSuccess(null, aResult.result);
  242.     },
  243.     onError: function onError(aError) {
  244.       aListener.onError(null, "", null);
  245.     }
  246.   }
  247.   this.authCallJSON(pingListener, "facebook.session.ping");
  248. }
  249.  
  250. facebookAPI.prototype.authenticate =
  251. function facebookAPI_authenticate(aListener) {
  252.   this._logger.debug("authenticate()");
  253.   var api = this;
  254.   var tokenListener = {
  255.     onSuccess: function(aToken) {
  256.       var authUrl = api.getAuthUrl(aToken);
  257.  
  258.       var req = CC[XMLHTTPREQUEST_CONTRACTID].
  259.                 createInstance(CI.nsIXMLHttpRequest);
  260.  
  261.       // We try to authenticate in the normal fashion (apiAuth2) first.
  262.       // If that fails, it may be because we have yet to agree to the
  263.       // Facebook Platform User Terms of Service.  apiAuth1 handles that case.
  264.       var onReadyStateFunc = function onReadyStateFunc(eEvt) {
  265.         if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  266.           if (api.webDetective.detectNoDOM("facebook", "apiAuth2", "",
  267.                                            req.responseText, null))
  268.           {
  269.             api.getSession(aToken, aListener);
  270.           } else {
  271.             api._logger.debug("req: \n" + req.responseText);
  272.             var results = new MyBag();
  273.             if (api.webDetective.detectNoDOM("facebook",
  274.                                              "apiAuth1",
  275.                                              "",
  276.                                              req.responseText,
  277.                                              results))
  278.             {
  279.               var postBody = "grant_perm=1"
  280.                            + "&next="
  281.                            + "&api_key="
  282.                            + FACEBOOK_API_KEY
  283.                            + "&auth_token="
  284.                            + aToken
  285.                            + "&post_form_id="
  286.                            + results.getPropertyAsAString("formId");
  287.  
  288.               var req2 = CC[XMLHTTPREQUEST_CONTRACTID].
  289.                          createInstance(CI.nsIXMLHttpRequest);
  290.  
  291.               req2.onreadystatechange = function (eEvt) {
  292.                 if (req2.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  293.                   if (api.webDetective.detectNoDOM("facebook",
  294.                                                    "apiAuth2",
  295.                                                    "",
  296.                                                    req2.responseText,
  297.                                                    null))
  298.                   {
  299.                     api.getSession(aToken, aListener);
  300.                   } else {
  301.                     api._logger.debug(req.responseText);
  302.                     var error = CC[FLOCK_ERROR_CONTRACTID]
  303.                                 .createInstance(CI.flockIError);
  304.                     error.errorString = "apiAuth2 Failed.";
  305.                     api._logger.debug("apiAuth2 Failed.");
  306.                     aListener.onError(null, null, error);
  307.                   }
  308.                 }
  309.               }
  310.  
  311.               req2.detachLoadGroup = true;
  312.               req2.open("POST", results.getPropertyAsAString("postUrl"), true);
  313.               req2.setRequestHeader("Content-Type",
  314.                                     "application/x-www-form-urlencoded");
  315.               req2.overrideMimeType("text/txt");
  316.               req2.send(postBody);
  317.             } else {
  318.               var error = CC[FLOCK_ERROR_CONTRACTID]
  319.                           .createInstance(CI.flockIError);
  320.               error.errorString = "apiAuth1 Failed.";
  321.               api._logger.debug("apiAuth1 Failed.");
  322.               aListener.onError(null, null, error);
  323.             }
  324.           }
  325.         }
  326.       }
  327.  
  328.       req.onreadystatechange = onReadyStateFunc;
  329.       req.detachLoadGroup = true;
  330.       req.open("GET", authUrl, true);
  331.       req.overrideMimeType("text/txt");
  332.       req.send(null);
  333.     },
  334.     onError: function onError(aErrorCode) {
  335.       aListener.onError(null, "", aErrorCode);
  336.     }
  337.   };
  338.   this.createToken(tokenListener);
  339. }
  340.  
  341. facebookAPI.prototype.deauthenticate =
  342. function facebookAPI_deauthenticate() {
  343.   this._logger.debug(".deauthenticate()");
  344.   this.session_key = null;
  345.   this.secret = null;
  346.   this.uid = null;
  347.   this._friendsCache.value = null;
  348.   this.isLoggedIn = false;
  349. }
  350.  
  351.  
  352. // INTERNAL
  353. facebookAPI.prototype.createToken =
  354. function facebookAPI_createToken(aListener) {
  355.   this._logger.debug("createToken()\n");
  356.   var api = this;
  357.   var tokenListener = {
  358.     onSuccess: function onSuccess(aXml) {
  359.       api._logger.debug(".createToken().onSuccess " + aXml);
  360.       try {
  361.         var token = aXml.getElementsByTagName("token")[0].firstChild.nodeValue;
  362.         aListener.onSuccess(token);
  363.       } catch (err) {
  364.         try {
  365.           var token = aXml.getElementsByTagName("auth_createToken_response")[0]
  366.                           .firstChild
  367.                           .nodeValue;
  368.           aListener.onSuccess(token);
  369.         } catch (err) {
  370.           var error = CC[FLOCK_ERROR_CONTRACTID]
  371.                       .createInstance(CI.flockIError);
  372.           error.errorString = err;
  373.           aListener.onError(null, "", error);
  374.         }
  375.       }
  376.     },
  377.     onError: function onError(aErrorCode) {
  378.       aListener.onError(null, "", aErrorCode);
  379.     }
  380.   };
  381.   this.call(tokenListener, "facebook.auth.createToken");
  382. }
  383.  
  384. // INTERNAL
  385. facebookAPI.prototype.getSession =
  386. function facebookAPI_getSession(aToken, aListener) {
  387.   var api = this;
  388.   var aParams = {
  389.     auth_token: aToken
  390.   };
  391.   var sessionListener = {
  392.     onSuccess: function onSuccess(aXml) {
  393.       api.session_key = aXml.getElementsByTagName("session_key")[0]
  394.                             .firstChild
  395.                             .nodeValue;
  396.       api.secret = aXml.getElementsByTagName("secret")[0]
  397.                        .firstChild
  398.                        .nodeValue;
  399.       api.uid = aXml.getElementsByTagName("uid")[0]
  400.                     .firstChild
  401.                     .nodeValue;
  402.       api._friendsCache.value = null;
  403.       api.isLoggedIn = true;
  404.       aListener.onSuccess(null, "authenticated")
  405.     },
  406.     onError: function onError(aErrorCode) {
  407.       api.isLoggedIn = false;
  408.       aListener.onError(null, "", aErrorCode);
  409.     }
  410.   };
  411.   api.isLoggedIn = false;
  412.   api.call(sessionListener, "facebook.auth.getSession", aParams);
  413. }
  414.  
  415. // INTERNAL
  416. facebookAPI.prototype.getAuthUrl =
  417. function facebookAPI_getAuthUrl(aToken) {
  418.   return "http://www.facebook.com/login.php?api_key=" + this.api_key
  419.          + "&v=" + FACEBOOK_API_VERSION + "&auth_token=" + aToken;
  420. }
  421.  
  422. facebookAPI.prototype.getFacebookURL =
  423. function facebookAPI_getFacebookURL(aUrlType, aFriendId) {
  424.  
  425.   switch (aUrlType) {
  426.     case "profile":
  427.       return "http://www.facebook.com/profile.php?uid="
  428.              + aFriendId
  429.              + "&api_key="
  430.              + this.api_key;
  431.       break;
  432.     case "myprofile":
  433.       return "http://www.facebook.com/profile.php?id="
  434.              + aFriendId
  435.              + "&api_key="
  436.              + this.api_key;
  437.       break;
  438.     case "poke":
  439.       return "http://www.facebook.com/poke.php?uid="
  440.              + aFriendId
  441.              + "&api_key="
  442.              + this.api_key;
  443.       break;
  444.     case "message":
  445.       return "http://www.facebook.com/message.php?uid="
  446.              + aFriendId
  447.              + "&api_key="
  448.              + this.api_key;
  449.       break;
  450.     case "editprofile":
  451.       return "http://www.facebook.com/editprofile.php";
  452.       break;
  453.     case "photos":
  454.       return "http://www.facebook.com/photo_search.php?uid="
  455.              + aFriendId
  456.              + "&api_key="
  457.              + this.api_key;
  458.       break;
  459.     case "userphotos":
  460.       return "http://www.facebook.com/photo_search.php?uid="
  461.              + aFriendId
  462.              + "&api_key="
  463.              + this.api_key;
  464.       break;
  465.     case "myphotos":
  466.       return "http://www.facebook.com/photos.php?id="
  467.              + aFriendId;
  468.       break;
  469.     case "postwall":
  470.       return "http://www.facebook.com/wallpost.php?uid="
  471.              + aFriendId
  472.              + "&api_key="
  473.              + this.api_key;
  474.       break;
  475.     case "addfriend":
  476.       return "http://www.facebook.com/addfriend.php?uid="
  477.              + aFriendId
  478.              + "&api_key="
  479.              + this.api_key;
  480.       break;
  481.     case "friendrequests":
  482.       return "http://www.facebook.com/reqs.php";
  483.       break;
  484.     case "messages":
  485.       return "http://www.facebook.com/mailbox.php";
  486.       break;
  487.     case "homepage":
  488.       return "http://www.facebook.com/home.php";
  489.       break;
  490.   }
  491.   return "";
  492. }
  493.  
  494. //****************************************
  495. //* Friends Calls
  496. //****************************************/
  497.  
  498.  
  499. facebookAPI.prototype.friendsGet =
  500. function facebookAPI_friendsGet(aListener) {
  501.   var api = this;
  502.   var listener = {
  503.     onSuccess: function listener_onSuccess(aResult) {
  504.       var peeps = {};
  505.       for (var i in aResult) {
  506.         var uid = aResult[i].uid;
  507.         peeps[uid] = aResult[i];
  508.  
  509.         if (!peeps[uid].pic_square) {
  510.           peeps[uid].pic_square = "";
  511.         }
  512.  
  513.         if (!peeps[uid].status) {
  514.           peeps[uid].status = {
  515.             time: 0,
  516.             message: ""
  517.           }
  518.         }
  519.  
  520.         api._logger.debug("Got facebook person named "
  521.                           + peeps[uid].name);
  522.       }
  523.  
  524.       var result = {
  525.         wrappedJS: peeps
  526.       };
  527.       aListener.onSuccess(result, "success");
  528.     },
  529.     onError: function(errorCode) {
  530.       api._logger.debug(".friendsGet() error: " + errorCode);
  531.     }
  532.   }
  533.  
  534.   // For security let's make sure this.uid is what
  535.   // it's supposed to be: just a number
  536.   _validateUid(this.uid);
  537.  
  538.   var friendsQuery = "SELECT uid,name,pic_square,status,profile_update_time "
  539.             + "FROM user "
  540.             + "WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 = "+this.uid+")";
  541.  
  542.   this.authCallJSON(listener, "facebook.fql.query", { query: friendsQuery });
  543. }
  544.  
  545.  
  546. facebookAPI.prototype.getUpdatedMediaFriends =
  547. function facebookAPI_getUpdatedMediaFriends(aListener, aLastSeen) {
  548.   var api = this;
  549.   var listener = {
  550.     onSuccess: function gumfListener_onSuccess(aResult) {
  551.       // We put everything in a hash first to avoid duplicates
  552.       var friends = {};
  553.       for (var i in aResult) {
  554.         var owner = aResult[i]["owner"];
  555.         // 10 for Base 10
  556.         var created = parseInt(aResult[i]["created"], 10);
  557.         if (friends[owner]) {
  558.           friends[owner].count++;
  559.           if (created > friends[owner].latest) {
  560.             friends[owner].latest = created;
  561.           }
  562.         } else {
  563.           friends[owner] = {
  564.             count: 1,
  565.             latest: created
  566.           };
  567.         }
  568.       }
  569.       var result = [];
  570.       for (i in friends) {
  571.         var person = new MyBag();
  572.         person.setPropertyAsAString("uid", i);
  573.         person.setPropertyAsInt32("count", friends[i].count);
  574.         person.setPropertyAsInt32("latest", friends[i].latest);
  575.         result.push(person);
  576.       }
  577.       aListener.onSuccess(createEnum(result), "success");
  578.     },
  579.     onError: function gumfListener_onError(errorCode) {
  580.       api._logger.debug(".getUpdatedMediaFriends() error: " + errorCode);
  581.     }
  582.   }
  583.  
  584.   // For security let's make sure this.uid is what
  585.   // it's supposed to be: just a number
  586.   _validateUid(this.uid);
  587.  
  588.   var friendsQuery = "SELECT uid1 "
  589.                    + "FROM friend "
  590.                    + "WHERE uid2=" + this.uid;
  591.   var albumQuery = "SELECT aid "
  592.                  + "FROM album "
  593.                  + "WHERE owner IN " + "(" + friendsQuery + ")";
  594.   var query = "SELECT owner,created "
  595.             + "FROM photo "
  596.             + "WHERE created > " + aLastSeen
  597.               + " AND " + "aid IN (" + albumQuery + ")";
  598.  
  599.   this.authCallJSON(listener, "facebook.fql.query", { query: query });
  600. };
  601.  
  602. //****************************************
  603. //* Users Info Call
  604. //****************************************/
  605.  
  606. facebookAPI.prototype.usersGetInfo =
  607. function facebookAPI_usersGetInfo(aListener, users, fields) {
  608.   var params = {
  609.     uids: users,
  610.     fields: "name,pic_square,status"
  611.   }
  612.  
  613.   if (fields) {
  614.     this._logger.debug("WARNING - arbitrary fields are not handled yet");
  615.     params.fields = fields;
  616.   }
  617.  
  618.   var api = this;
  619.   var listener = {
  620.     onSuccess: function ugiListener_onSuccess(aResult) {
  621.       var peeps = [];
  622.       for (var i in aResult) {
  623.         var user = aResult[i];
  624.         var person = new MyBag();
  625.         person.setPropertyAsAString("uid", user.uid);
  626.         person.setPropertyAsAString("name", user.name);
  627.         person.setPropertyAsAString("avatar", user.pic_square);
  628.         if (user.status) {
  629.           person.setPropertyAsAString("statusMessage",
  630.                                       user.status.message);
  631.         }
  632.  
  633.         peeps.push(person);
  634.       }
  635.  
  636.       aListener.onSuccess(createEnum(peeps), "success");
  637.     },
  638.     onError: function ugiListener_onError(aError) {
  639.       api._logger.debug(".usersGetInfo() error:" + aError.errorString);
  640.     }
  641.   }
  642.  
  643.   this.authCallJSON(listener, "facebook.users.getInfo", params);
  644. }
  645.  
  646.  
  647. facebookAPI.prototype.notificationsGet =
  648. function facebookAPI_notificationsGet(aListener) {
  649.   // FIXME: Use JSON or at least E4X (bug 9573)
  650.   var api = this;
  651.   var listener = {
  652.     onSuccess: function ngListener_onSuccess(aXml) {
  653.       var notifications = [];
  654.  
  655.       var meNotifications = new MyBag();
  656.       meNotifications.setPropertyAsAString("messages",
  657.         aXml.getElementsByTagName("messages")[0]
  658.             .getElementsByTagName("unread")[0]
  659.             .childNodes[0]
  660.             .nodeValue);
  661.       meNotifications.setPropertyAsAString("pokes",
  662.         aXml.getElementsByTagName("pokes")[0]
  663.             .getElementsByTagName("unread")[0]
  664.             .firstChild
  665.             .nodeValue);
  666.       meNotifications.setPropertyAsAString("friendRequests",
  667.         aXml.getElementsByTagName("friend_requests")[0]
  668.             .getElementsByTagName("uid")
  669.             .length);
  670.       meNotifications.setPropertyAsAString("groupInvites",
  671.         aXml.getElementsByTagName("group_invites")[0]
  672.             .getElementsByTagName("gid")
  673.             .length);
  674.       meNotifications.setPropertyAsAString("eventInvites",
  675.         aXml.getElementsByTagName("event_invites")[0]
  676.             .getElementsByTagName("eid")
  677.             .length);
  678.       notifications.push(meNotifications);
  679.  
  680.       aListener.onSuccess(createEnum(notifications), "success");
  681.     },
  682.     onError: function ngListener_onError(aError) {
  683.       api._logger.debug(".notificationsGet() error:" + aError.errorString);
  684.     }
  685.   };
  686.  
  687.   this.authCall(listener, "facebook.notifications.get");
  688. }
  689.  
  690.  
  691. facebookAPI.prototype.setStatus =
  692. function facebookAPI_setStatus(aNewStatus, aListener) {
  693.   var api = this;
  694.  
  695.   function reallySetStatus(aPostFormId) {
  696.     var params = {
  697.       post_form_id: aPostFormId
  698.     };
  699.  
  700.     if (aNewStatus && aNewStatus != "") {
  701.       params.status = aNewStatus;
  702.     } else {
  703.       params.clear = "1";
  704.     }
  705.  
  706.     var body = api.getParamString(params);
  707.  
  708.     var xhr2 = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
  709.                .createInstance(CI.nsIXMLHttpRequest);
  710.     xhr2.open ("POST", FACEBOOK_API_SETSTATUS_URL, true);
  711.     xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  712.     xhr2.setRequestHeader("Content-Length", body.length);
  713.  
  714.     xhr2.onreadystatechange = function (aEvent) {
  715.       if (xhr2.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  716.         if ((xhr2.status != HTTP_CODE_OK) &&
  717.             (xhr2.status != HTTP_CODE_FOUND))
  718.         {
  719.           if (aListener) {
  720.             aListener.onError(xhr2.statusText);
  721.           }
  722.           return;
  723.         }
  724.         if (aListener) {
  725.           aListener.onSuccess(null, "");
  726.         }
  727.       }
  728.     }
  729.  
  730.     xhr2.send(body);
  731.   }
  732.  
  733.   function viewTOS() {
  734.     // Show the TOS and to get the form
  735.     var xhr = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
  736.               .createInstance(CI.nsIXMLHttpRequest);
  737.     xhr.open("GET", FACEBOOK_API_TOS_URL, true);
  738.     xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  739.  
  740.     xhr.onreadystatechange = function (aEvent) {
  741.       if (xhr.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  742.         if (xhr.status != HTTP_CODE_OK) {
  743.           if (aListener) {
  744.             aListener.onError (xhr.statusText);
  745.           }
  746.           return;
  747.         }
  748.  
  749.         // Get the post_form_id and setStatus for real
  750.         var postFormId;
  751.         var results = CC["@mozilla.org/hash-property-bag;1"]
  752.                       .createInstance(CI.nsIWritablePropertyBag2);
  753.         try {
  754.           if (api.webDetective.detectNoDOM("facebook",
  755.                                            "setStatus",
  756.                                            "",
  757.                                            xhr.responseText,
  758.                                            results)) 
  759.           {
  760.             postFormId = results.getPropertyAsAString("status");
  761.             reallySetStatus(postFormId);
  762.           }
  763.         } catch (ex) {
  764.           aListener.onError();
  765.         }
  766.       }
  767.     }
  768.     xhr.send(null);
  769.   }
  770.  
  771.   viewTOS();
  772. }
  773.  
  774.  
  775. //****************************************
  776. //* Photo Call
  777. //****************************************/
  778.  
  779. facebookAPI.prototype.getAlbums =
  780. function facebookAPI_getAlbums(aListener, userID) {
  781.   var api = this;
  782.   var listener = {
  783.     onSuccess: function(xml) {
  784.       var albums = [];
  785.       var eAlbums = xml.getElementsByTagName('album')
  786.       for (var i=0; i<eAlbums.length; i++) {
  787.         var id = eAlbums[i].getElementsByTagName('aid')[0]
  788.                            .firstChild
  789.                            .nodeValue;
  790.         var title = eAlbums[i].getElementsByTagName('name')[0]
  791.                               .firstChild
  792.                               .nodeValue;
  793.  
  794.         var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
  795.                        .createInstance(CI.flockIPhotoAlbum);
  796.         newAlbum.id = id;
  797.         newAlbum.title = title;
  798.         api._logger.debug(".getAlbums() Found album: title = " + title +
  799.                           ", id = " + id);
  800.         albums.push(newAlbum);
  801.       }
  802.       aListener.onSuccess(createEnum(albums), 'success');
  803.     },
  804.     onError: function(aFlockError) {
  805.       api._logger.debug(".getAlbums() error: " + aFlockError.errorString);
  806.       aListener.onError(null, null, aFlockError);
  807.     }
  808.   }
  809.   var params = {
  810.     uid: userID
  811.   }
  812.   this.authCall(listener, 'facebook.photos.getAlbums', params);
  813. }
  814.  
  815.  
  816. facebookAPI.prototype.createAlbum =
  817. function facebookAPI_createAlbum(aListener, aAlbumTitle) {
  818.   var api = this;
  819.   var listener = {
  820.     onSuccess: function ca_onSuccess(aXml) {
  821.       var id = aXml.getElementsByTagName("aid")[0].firstChild.nodeValue;
  822.       var title = aXml.getElementsByTagName("name")[0].firstChild.nodeValue;
  823.  
  824.       var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
  825.                      .createInstance(CI.flockIPhotoAlbum);
  826.       newAlbum.title = title;
  827.       newAlbum.id = id;
  828.       aListener.onSuccess(newAlbum, "success");
  829.     },
  830.     onError: function ca_onError(aFlockError) {
  831.       api._logger.debug(".createAlbum() error: " + aFlockError.errorString);
  832.       aListener.onError(aFlockError);
  833.     }
  834.   }
  835.   var params = {
  836.     name: aAlbumTitle
  837.   }
  838.   this.authCall(listener, "facebook.photos.createAlbum", params);
  839. }
  840.  
  841.  
  842. facebookAPI.prototype.getPhotos =
  843. function facebookAPI_getPhotos(aListener, aSubjectId, aAlbumId, aPidList) {
  844.   var api = this;
  845.   var listener = {
  846.     onSuccess: function gp_onSuccess(aResult) {
  847.       var photos = [];
  848.       for (var i in aResult) {
  849.         var photo = aResult[i];
  850.  
  851.         var newPhoto = new facebookPhoto();
  852.         newPhoto.id = photo.pid;
  853.         newPhoto.webPageUrl = photo.link;
  854.         newPhoto.thumbnail = photo.src_small;
  855.         newPhoto.midSizePhoto = photo.src;
  856.         newPhoto.largeSizePhoto = photo.src_big;
  857.         newPhoto.userid = photo.owner;
  858.         newPhoto.title = photo.caption ? photo.caption : "";
  859.         newPhoto.uploadDate = parseInt(photo.created) * 1000;
  860.         // FIXME: need to get the full name and avatar of user - DP
  861.         newPhoto.username = photo.owner;
  862.         newPhoto.icon = FLOCK_SNOWMAN_URL;
  863.  
  864.         photos.push(newPhoto);
  865.       }
  866.       aListener.onSuccess(createEnum(photos), 'success');
  867.     },
  868.     onError: function gp_onError(flockError) {
  869.       api._logger.debug(".getPhotos() error: " + flockError.errorString);
  870.       aListener.onError(null, null, flockError);
  871.     }
  872.   };
  873.   var params = {};
  874.   if (aSubjectId) {
  875.     params.subj_id = aSubjectId;
  876.   }
  877.   if (aAlbumId) {
  878.     params.aid = aAlbumId;
  879.   }
  880.   if (aPidList) {
  881.     params.pids = aPidList;
  882.   }
  883.   this.authCallJSON(listener, "facebook.photos.get", params);
  884. }
  885.  
  886.  
  887. facebookAPI.prototype._finalizePhoto =
  888. function facebookAPI__finalizePhoto(aUploadListener,
  889.                                     aUpload,
  890.                                     aId)
  891. {
  892.   var getPhotoListener = {
  893.     onSuccess: function fp_onSuccess(aPhotos) {
  894.       if (aPhotos.hasMoreElements()) {
  895.         var photo = aPhotos.getNext();
  896.         photo.QueryInterface(CI.flockIPhoto);
  897.         aUploadListener.onUploadFinalized(aUpload, photo);
  898.       } else {
  899.         // Empty result
  900.         aUploadListener.onError(null);
  901.       }
  902.     },
  903.     onError: function fp_onError(aError) {
  904.       aUploadListener.onError(null);
  905.     }
  906.   };
  907.  
  908.   this.getPhotos(getPhotoListener, null, null, aId);
  909. }
  910.  
  911. facebookAPI.prototype.uploadPhotosViaUploader =
  912. function facebookAPI_uploadPhotosViaUploader(aListener,
  913.                                              aPhoto,
  914.                                              aParams,
  915.                                              aUpload) {
  916.   var api = this;
  917.  
  918.   var pid;
  919.   var listener = {
  920.     onResult: function listener_onResult(aXml) {
  921.       api._logger.debug(CC["@mozilla.org/xmlextras/xmlserializer;1"]
  922.                         .getService(CI.nsIDOMSerializer)
  923.                         .serializeToString(aXml));
  924.       var resp = aXml.getElementsByTagName("photos_upload_response")[0];
  925.       if (!resp) {
  926.         var error = api.getXMLError(aXml);
  927.         aListener.onError(error);
  928.       } else {
  929.         pid = aXml.getElementsByTagName("pid")[0].childNodes[0].nodeValue;
  930.  
  931.         // tag photo if user requested
  932.         if (aUpload.notes) {
  933.           var addTagListener = {
  934.             onSuccess: function addTagListener_success(aXml, aStatus) {
  935.               api._logger.debug("tagged successfully\n");
  936.             },
  937.             onError: function addTagListener_error(aSubject, aTopic, aError) {
  938.               api._logger.error("error tagging photo\n");
  939.             }
  940.           };
  941.           api.addTag(addTagListener, pid, "", "", "", "", aUpload.notes);
  942.         }
  943.  
  944.         aListener.onUploadComplete(aUpload);
  945.         api._finalizePhoto(aListener, aUpload, pid);
  946.       }
  947.     },
  948.     onError: function listener_onError(aErrorCode) {
  949.       if (aErrorCode) {
  950.         aListener.onError(api.getHTTPError(aErrorCode));
  951.       } else {
  952.         aListener.onError(api.getError(null, null));
  953.       }
  954.     },
  955.     onProgress: function listener_onProgress(aCurrentProgress) {
  956.       aListener.onProgress(aCurrentProgress);
  957.     }
  958.   };
  959.  
  960.   var params = {};
  961.   params.method = "facebook.photos.upload";
  962.   params.api_key = this.api_key;
  963.   params.session_key = this.session_key;
  964.   params.call_id = new Date().getTime();
  965.   params.v = FACEBOOK_API_VERSION;
  966.   params.aid = aUpload.album;
  967.   params.caption = aParams.getProperty("description");
  968.  
  969.   var uploader = new PhotoUploader();
  970.  
  971.   params = api.appendSignature(params, api.secret);
  972.   uploader.setEndpoint(FACEBOOK_API_ENDPOINT_URL);
  973.   uploader.upload(listener, aPhoto, params);
  974.  
  975.   return;
  976. }
  977.  
  978. facebookAPI.prototype.addTag =
  979. function facebookAPI_addTag(aListener, aPid, aTagUid, aTagText, aX, aY, aTags) {
  980.   this._logger.debug(".addTag()");
  981.   var json;
  982.   if (aTags) {
  983.     json = aTags;
  984.   } else {
  985.     json = '[{"x":"' + aX + '","y":"' + aY + '"';
  986.     if (aTagUid) {
  987.       json += ',"tag_uid":' + aTagUid;
  988.     } else if (aTagText) {
  989.       json += ',"tag_text":"' + aTagText + '"';
  990.     }
  991.     json += '}]';
  992.   }
  993.   this._logger.debug("tag json: " + json);
  994.  
  995.   var myListener = {
  996.     onSuccess : function(xml) {
  997.       aListener.onSuccess(xml, "success");
  998.     },
  999.     onError: function(aFlockError) {
  1000.       aListener.onError(null, null, aFlockError);
  1001.     }
  1002.   };
  1003.  
  1004.   var params = {
  1005.     pid: aPid,
  1006.     tags: json
  1007.   };
  1008.   this.authCall(myListener, "facebook.photos.addTag", params);
  1009. }
  1010.  
  1011. //****************************************
  1012. //* FQL Query calls
  1013. //****************************************/
  1014. facebookAPI.prototype.doFQLQuery=
  1015. function facebookAPI_doFQLQuery(aListener, aFQLQuery) {
  1016.   
  1017.   var myListener = {
  1018.     onSuccess : function(xml) {
  1019.       aListener.onSuccess(xml, "success");
  1020.     },
  1021.     onError: function(aFlockError) {
  1022.       aListener.onError(null, null, aFlockError);
  1023.     }
  1024.   };
  1025.  
  1026.   var params = {
  1027.     format: "xml",
  1028.     query: aFQLQuery
  1029.   };
  1030.   this.authCall(myListener, 'facebook.fql.query', params);
  1031. }
  1032.  
  1033.  
  1034. //******************************************
  1035. //* internal functions for making requests
  1036. //******************************************
  1037.  
  1038. /* --- authCall is used for authenticated (most) calls --- */
  1039.  
  1040. facebookAPI.prototype.authCall =
  1041. function facebookAPI_authCall(aListener, aMethod, aParams, aIsDryRun) {
  1042.   if (!aParams) aParams = {};
  1043.   aParams.api_key = this.api_key;
  1044.   aParams.session_key = this.session_key;
  1045.   aParams.call_id = new Date().getTime();
  1046.   aParams.method = aMethod;
  1047.   aParams.v = FACEBOOK_API_VERSION;
  1048.   var paramString = this.getParamString(aParams, this.secret);
  1049.   var url = this.endpoint + "?" + paramString;
  1050.   this._logger.debug("===[" + aMethod + "]===> " + url);
  1051.   if (!aIsDryRun) {
  1052.     this._doCall(aListener, url, null);
  1053.   }
  1054. }
  1055.  
  1056. facebookAPI.prototype.authCallJSON =
  1057. function facebookAPI_authCallJSON(aListener, aMethod, aParams, aIsDryRun) {
  1058.   if (!aParams) aParams = {};
  1059.   aParams.api_key = this.api_key;
  1060.   aParams.session_key = this.session_key;
  1061.   aParams.call_id = new Date().getTime();
  1062.   aParams.method = aMethod;
  1063.   aParams.format = "JSON";
  1064.   aParams.v = FACEBOOK_API_VERSION;
  1065.   var paramString = this.getParamString(aParams, this.secret);
  1066.   var url = this.endpoint + "?" + paramString;
  1067.   this._logger.debug("===[" + aMethod + "]===> " + url);
  1068.   if (!aIsDryRun) {
  1069.     this._doCallJSON(aListener, url, null);
  1070.   }
  1071. }
  1072.  
  1073. /* --- call is only used to start authentication by getting a token --- */
  1074.  
  1075. facebookAPI.prototype.call =
  1076. function facebookAPI_call(aListener, aMethod, aParams, aIsDryRun) {
  1077.   if (!aParams) aParams = {};
  1078.   aParams.api_key = this.api_key;
  1079.   aParams.method = aMethod;
  1080.   aParams.v = FACEBOOK_API_VERSION;
  1081.   var paramString = this.getParamString(aParams, this.api_secret);
  1082.   var url = this.endpoint + "?" + paramString;
  1083.   this._logger.debug("===[" + aMethod + "]===> " + url);
  1084.   if (!aIsDryRun) {
  1085.     this._doCall(aListener, url, null);
  1086.   }
  1087. }
  1088.  
  1089. /* --- convert dictionary to a request string, adding the signature --- */
  1090.  
  1091. facebookAPI.prototype.getParamString =
  1092. function facebookAPI_getParamString(aParams, secret) {
  1093.   var params = this.appendSignature(aParams, secret);
  1094.   var rval = "";
  1095.  
  1096.   var count = 0;
  1097.   for(var p in params) {
  1098.     if(count++!=0) rval += "&";
  1099.     rval += encodeURIComponent(p) + "=" + encodeURIComponent(params[p]);
  1100.   }
  1101.  
  1102.   return rval;
  1103. };
  1104.  
  1105. /* --- calculate and add the signature to the request parameters --- */
  1106.  
  1107. facebookAPI.prototype.appendSignature =
  1108. function facebookAPI_appendSignature(aParams, secret) {
  1109.   var keys = new Array();
  1110.  
  1111.   for (var p in aParams) keys.push(p);
  1112.   keys.sort();
  1113.  
  1114.   var preHash = '';
  1115.   for (var i=0; i<keys.length; ++i) preHash += keys[i] + '=' + aParams[keys[i]];
  1116.   preHash += secret;
  1117.  
  1118.   var converter = CC["@mozilla.org/intl/scriptableunicodeconverter"]
  1119.                   .createInstance(CI.nsIScriptableUnicodeConverter);
  1120.  
  1121.   converter.charset = "UTF-8";
  1122.   var inputStream = converter.convertToInputStream(preHash);
  1123.   var newParams = aParams;
  1124.   newParams.sig = FlockCryptoHash.md5Stream(inputStream);
  1125.   return newParams;
  1126. };
  1127.  
  1128.  
  1129. facebookAPI.prototype.getHTTPError =
  1130. function facebookAPI_getHTTPError(aHTTPErrorCode) {
  1131.   var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  1132.   error.errorCode = aHTTPErrorCode;
  1133.  
  1134.   return error;
  1135. }
  1136.  
  1137. facebookAPI.prototype.getXMLError =
  1138. function facebookAPI_getXMLError(aXML) {
  1139.   // FIXME: Use E4X (bug 9573)
  1140.   var fbErrorCode;
  1141.   var fbErrorMessage;
  1142.  
  1143.   // <error_response xmlns="http://api.facebook.com/1.0/">
  1144.   //   <error_code>324</error_code>
  1145.   //   <error_msg>Missing or invalid image file</error_msg>
  1146.   //   <request_args/>
  1147.   // </error_response>
  1148.   try {
  1149.     fbErrorCode = aXML.getElementsByTagName("error_code")[0]
  1150.                       .childNodes[0].nodeValue;
  1151.     fbErrorMessage = aXML.getElementsByTagName("error_msg")[0]
  1152.                          .childNodes[0].nodeValue;
  1153.   } catch (ex) {
  1154.     // <result method="" type="struct">
  1155.     //   <fb_error type="struct">
  1156.     //     <code>101</code>
  1157.     //     <msg>Invalid API key</msg>
  1158.     //     <your_request/>
  1159.     //   </fb_error>
  1160.     // </result>
  1161.     try {
  1162.       fbErrorCode = aXML.getElementsByTagName("code")[0]
  1163.                         .childNodes[0].nodeValue;
  1164.       fbErrorMessage = aXML.getElementsByTagName("msg")[0]
  1165.                            .childNodes[0].nodeValue;
  1166.     } catch (ex2) {
  1167.       // in case the error xml is invalid
  1168.       fbErrorCode = "999";
  1169.     }
  1170.   }
  1171.  
  1172.   return this.getError(fbErrorCode, fbErrorMessage);
  1173. }
  1174.  
  1175.  
  1176. facebookAPI.prototype.getError =
  1177. function facebookAPI_getError(aFBErrorCode, aFBErrorMessage) {
  1178.   var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
  1179.  
  1180.   this._logger.debug(".getError() Facebook error code: " + aFBErrorCode + "\n");
  1181.   switch (aFBErrorCode) {
  1182.     case "2":
  1183.       // 2: The service is not available at this time.
  1184.       error.errorCode = CI.flockIError.PHOTOSERVICE_UNAVAILABLE;
  1185.       break;
  1186.     case "100":
  1187.       // 100: One of the parameters specified was missing or invalid.
  1188.     case "103":
  1189.       // 103: The submitted call_id was not greater than the previous call_id
  1190.       //      for this session.
  1191.     case "104":
  1192.       // 104: Incorrect signature.
  1193.       error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_QUERY;
  1194.       break;
  1195.     case "101":
  1196.       // 101: The api key submitted is not associated with any known
  1197.       //      application.
  1198.       error.errorCode = CI.flockIError.PHOTOSERVICE_INVALID_API_KEY;
  1199.       break;
  1200.     case "102":
  1201.       // 102: The session key was improperly submitted or has reached its
  1202.       //      timeout. Direct the user to log in again to obtain another key.
  1203.       error.errorCode = CI.flockIError.PHOTOSERVICE_USER_NOT_LOGGED_IN;
  1204.       error.serviceName = this.prettyName;
  1205.       break;
  1206.     case "321":
  1207.       // 321: Album is full
  1208.       error.errorCode = CI.flockIError
  1209.                           .PHOTOSERVICE_PHOTOS_IN_ALBUM_LIMIT_REACHED;
  1210.       break;
  1211.     case "1":
  1212.       // 1: An unknown error occurred. Please resubmit the request.
  1213.     case "999":
  1214.       error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
  1215.       break;
  1216.     default:
  1217.       error.errorCode = CI.flockIError.PHOTOSERVICE_UNKNOWN_ERROR;
  1218.       error.serviceErrorString = serviceErrorMessage;
  1219.   }
  1220.   return error;
  1221. }
  1222.  
  1223. /* --- actually make the http request --- */
  1224.  
  1225. facebookAPI.prototype._doCall =
  1226. function facebookAPI_doCall(aListener, aUrl, aContent) {
  1227.   var api=this;
  1228.  
  1229.   // copied from flickr service --- potential issues:
  1230.   // 1) shouldn't be using xmlhttprequest
  1231.   // 2) keeping a reference to the request via this.req
  1232.   //    keeps it referenced after we are done with it
  1233.   //    (until the next call)
  1234.  
  1235.   this.req = CC[XMLHTTPREQUEST_CONTRACTID].
  1236.              createInstance(CI.nsIXMLHttpRequest);
  1237.   this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  1238.   this.req.open("GET", aUrl, true);
  1239.   var req = this.req;
  1240.   this.req.onreadystatechange = function (aEvt) {
  1241.     api._logger.debug("._doCall() ReadyState = " + req.readyState);
  1242.     if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  1243.       try {
  1244.         api._logger.debug("._doCall() Status = " + req.status);
  1245.         if(req.status/100 == 2) {
  1246.           try {
  1247.             api._logger.debug("._doCall() response = \n" + req.responseText);
  1248.             var fb_error = req.responseXML
  1249.                               .getElementsByTagName("error_response");
  1250.             if (fb_error.length > 0) {
  1251.               api._logger.debug("._doCall() Error");
  1252.               aListener.onError(api.getXMLError(req.responseXML));
  1253.             } else {
  1254.               api._logger.debug("._doCall() Success");
  1255.               aListener.onSuccess(req.responseXML);
  1256.             }
  1257.           } catch (ex) {
  1258.             // error parsing xml
  1259.             api._logger.error("._doCall() Parse error = " + ex);
  1260.             aListener.onError(api.getError(null, null));
  1261.           }
  1262.         } else {
  1263.           //  http errors
  1264.           api._logger.debug("._doCall() HTTP Error");
  1265.           aListener.onError(api.getHTTPError(req.status));
  1266.         }
  1267.       } catch (ex) {
  1268.         // XMLHTTPERROR (connection lost)
  1269.         api._logger.debug(".doCall() " + ex);
  1270.         aListener.onError(api.getHTTPError("9001"));
  1271.       }
  1272.     }
  1273.   }
  1274.   try {
  1275.     // just for debug output
  1276.     aUrl.match(/call_id=(.+)&/);
  1277.     var call_id = RegExp.$1;
  1278.     this._logger.debug("sending call_id: " + call_id);
  1279.   } catch (ex) {
  1280.   }
  1281.   this.req.send(null);
  1282. };
  1283.  
  1284. facebookAPI.prototype._doCallJSON =
  1285. function facebookAPI_doCallJSON(aListener, aUrl, aContent) {
  1286.   var api = this;
  1287.  
  1288.   this.req = CC[XMLHTTPREQUEST_CONTRACTID]
  1289.              .createInstance(CI.nsIXMLHttpRequest);
  1290.   this.req.QueryInterface(CI.nsIJSXMLHttpRequest);
  1291.   this.req.open("GET", aUrl, true);
  1292.   var req = this.req;
  1293.   this.req.onreadystatechange =
  1294.   function doCallJSON_onreadystatechange(aEvt) {
  1295.     api._logger.debug("._doCall() ReadyState = " + req.readyState);
  1296.     if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
  1297.       try {
  1298.         api._logger.debug("._doCall() Status = " + req.status);
  1299.         if (req.status/100 == 2) {
  1300.           api._logger.debug("._doCall() response = \n" + req.responseText);
  1301.           var s = new Components.utils.Sandbox("about:blank");
  1302.           var result = Components.utils.evalInSandbox(req.responseText, s);
  1303.           if (result && result.error_code && result.error_msg) {
  1304.             api._logger.debug("._doCall() Error");
  1305.             aListener.onError(api.getError(result.error_code,
  1306.                                            result.error_msg));
  1307.           } else {
  1308.             api._logger.debug("._doCall() Success");
  1309.             aListener.onSuccess(result);
  1310.           }
  1311.         } else {
  1312.           //  http errors
  1313.           api._logger.debug("._doCall() HTTP Error");
  1314.           aListener.onError(api.getHTTPError(req.status));
  1315.         }
  1316.       } catch (ex) {
  1317.         // XMLHTTPERROR (connection lost)
  1318.         api._logger.debug(".doCall() " + ex);
  1319.         aListener.onError(api.getHTTPError("9001"));
  1320.       }
  1321.     }
  1322.   }
  1323.   try {
  1324.     // just for debug output
  1325.     aUrl.match(/call_id=(.+)&/);
  1326.     var call_id = RegExp.$1;
  1327.     this._logger.debug("sending call_id: " + call_id);
  1328.   } catch (ex) {
  1329.   }
  1330.   this.req.send(null);
  1331. };
  1332.  
  1333.  
  1334. //*****************************************************
  1335. //* xpcom constructor/info
  1336. //*****************************************************
  1337.  
  1338.  
  1339. facebookAPI.prototype.flags = CI.nsIClassInfo.SINGLETON;
  1340. facebookAPI.prototype.classDescription = "Facebook JS API";
  1341. facebookAPI.prototype.getInterfaces = function (count) {
  1342.   var interfaceList = [
  1343.     CI.flockIFacebookAPI,
  1344.     CI.nsIClassInfo
  1345.   ];
  1346.   count.value = interfaceList.length;
  1347.   return interfaceList;
  1348. }
  1349.  
  1350. facebookAPI.prototype.getHelperForLanguage = function (count) { return null; }
  1351.  
  1352. facebookAPI.prototype.QueryInterface = function (iid) {
  1353.   if (!iid.equals(CI.flockIFacebookAPI) &&
  1354.       !iid.equals(CI.nsIClassInfo) &&
  1355.       !iid.equals(CI.nsISupports)) {
  1356.     throw CR.NS_ERROR_NO_INTERFACE;
  1357.   }
  1358.   return this;
  1359. }
  1360.  
  1361. facebookAPI.prototype.createPhoto = function () {
  1362.   return (new facebookPhoto().QueryInterface(CI.flockIPhoto));
  1363. }
  1364.  
  1365. var FB_API_Module = new Object();
  1366.  
  1367. FB_API_Module.registerSelf = function (compMgr, fileSpec, location, type) {
  1368.   compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);
  1369.  
  1370.   compMgr.registerFactoryLocation(FACEBOOK_API_CID,
  1371.                                   "Flock Facebook API JS Component",
  1372.                                   FACEBOOK_API_CONTRACTID,
  1373.                                   fileSpec,
  1374.                                   location,
  1375.                                   type);
  1376. }
  1377.  
  1378. FB_API_Module.getClassObject = function (compMgr, cid, iid) {
  1379.   if (!cid.equals(FACEBOOK_API_CID)) {
  1380.     throw CR.NS_ERROR_NO_INTERFACE;
  1381.   }
  1382.   if (!iid.equals(CI.nsIFactory)) {
  1383.     throw CR.NS_ERROR_NOT_IMPLEMENTED;
  1384.   }
  1385.   return FB_API_ServiceFactory;
  1386. }
  1387.  
  1388. FB_API_Module.canUnload = function (compMgr) {
  1389.   return true;
  1390. }
  1391.  
  1392. /* factory object */
  1393. var FB_API_ServiceFactory = new Object();
  1394.  
  1395. FB_API_ServiceFactory.createInstance = function (outer, iid) {
  1396.   if (outer != null) {
  1397.     throw CR.NS_ERROR_NO_AGGREGATION;
  1398.   }
  1399.   return (new facebookAPI()).QueryInterface(iid);
  1400. }
  1401.  
  1402. /* entrypoint */
  1403. function NSGetModule(compMgr, fileSpec) {
  1404.   return FB_API_Module;
  1405. }
  1406.